// ProjectName: go-learn-demo
// PackageName: elasticybxb
// Description: 案例：日志分析
// Author: hy.bai
// Date: 2025-09-03 星期三 16:48

package elasticybxb

import (
	"bufio"
	"context"
	"fmt"
	"log/slog"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/elastic/go-elasticsearch/v8/typedapi/types"
)

var nginxAccessLog = "nginx-access-log"

// 日志结构体
type AccessLog struct {
	Timestamp    time.Time `json:"timestamp"`
	Ip           string    `json:"ip"`
	Method       string    `json:"method"`
	URL          string    `json:"url"`
	StatusCode   int       `json:"status_code"`
	ResponseTime int       `json:"response_time"`
	UserAgent    string    `json:"user_agent"`
}

// 把日志文件中的内容逐条写入到es中
func ElasticDemoLogWriter() {
	// 1.创建ElasticSearch的客户端
	client, _ := AllInitElastic.InitElasticByUsername()
	// 2.读取日志文件
	file, err := os.Open("11_elastic/logs/access.log")
	if err != nil {
		slog.Error("ReadFile Error: %s", err.Error())
		return
	}
	defer file.Close()
	// 3.分析日志文件的内容，转换成日志结构体，
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		// 读取每一行日志
		line := scanner.Text()
		// 解析日志
		accessLog := parseLog(line)
		if accessLog != nil {
			// 逐条保存文档，效率不高
			resp, err := client.Index(nginxAccessLog).Request(accessLog).Do(context.TODO())
			if err != nil {
				slog.Error(fmt.Sprintf("Elastic Create Index Error: %s", err.Error()))
			}
			fmt.Println(resp)
		}
	}
}

// 把日志文件中的内容批量写入到es中
func ElasticDemoLogBatchWriter() {
	// 1.创建ElasticSearch的客户端
	client, _ := AllInitElastic.InitElasticByUsername()
	// 2.读取日志文件
	file, err := os.Open("11_elastic/logs/access.log")
	if err != nil {
		slog.Error("ReadFile Error: %s", err.Error())
		return
	}
	defer file.Close()
	// 3.分析日志文件的内容，转换成日志结构体，
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		// 读取每一行日志
		line := scanner.Text()
		// 解析日志
		accessLog := parseLog(line)
		if accessLog != nil {
			// 逐条保存文档，效率不高
			resp, err := client.Index(nginxAccessLog).Request(accessLog).Do(context.TODO())
			if err != nil {
				slog.Error(fmt.Sprintf("Elastic Create Index Error: %s", err.Error()))
			}
			fmt.Println(resp)
		}
	}
	// 4.批量写入es中
}

// 解析日志
func parseLog(line string) *AccessLog {
	parts := strings.Split(line, " ")
	if len(parts) < 7 {
		slog.Error("日志格式错误")
		return nil
	}
	// 解析时间戳
	timestamp := parseTime(parts[3][1:]) // [1:]作用是截取字符串,从第1个字符到最后
	// 解析UserAgent
	userAgent := strings.Join(parts[11:], " ")
	return &AccessLog{
		Timestamp:    timestamp,
		Ip:           parts[0],
		Method:       parts[5][1:], // 截取字符串
		URL:          parts[6],
		StatusCode:   parseToInt(parts[8]),            // 解析状态码
		ResponseTime: parseToInt(parts[9]),            // 解析响应时间
		UserAgent:    userAgent[1 : len(userAgent)-1], // 字符串拼接
	}
}

// 解析日期时间
func parseTime(timeStr string) time.Time {
	timestamp, err := time.Parse("02/Jan/2006:15:04:05", timeStr)
	if err != nil {
		slog.Error("时间转换失败,原因是:", err.Error())
	}
	return timestamp
}

// 解析字符串，转换成int类型
func parseToInt(str string) int {
	if str == "-" {
		return 0
	}
	data, err := strconv.Atoi(str)
	if err != nil {
		slog.Error("字符串转换成int类型失败,原因是:", err.Error())
	}
	return data
}

// 日志分析：分析各IP数量
func ElasticDemoLogAnalysis() {
	//
	client, _ := AllInitElastic.InitElasticByUsername()

	field := "Ip"
	size := 100

	aggregations := map[string]types.Aggregations{
		"ips": {
			Terms: &types.TermsAggregation{
				Field: &field,
				Size:  &size, // 限制返回前10个结果
			},
		},
	}
	// 创建查询,并执行查询
	resp, err := client.Search().
		Index(nginxAccessLog).
		Size(0).
		Aggregations(aggregations).
		Do(context.TODO())
	if err != nil {
		fmt.Println("聚合查询失败,原因是:", err.Error())
	}
	//  处理聚合结果
	if resp.Aggregations != nil {
		if ipsAgg, found := resp.Aggregations["ips"]; found && ipsAgg != nil {
			if termsAgg, ok := ipsAgg.(map[string]interface{}); ok {
				if buckets, exists := termsAgg["buckets"].([]interface{}); exists {
					for _, bucket := range buckets {
						if bucketMap, ok := bucket.(map[string]interface{}); ok {
							key, keyOk := bucketMap["key"].(string)
							docCount, countOk := bucketMap["doc_count"].(float64)
							if keyOk && countOk {
								fmt.Printf("IP: %s, Count: %.0f\n", key, docCount)
							}
						}
					}
				}
			}
		}
	}
}

func TestElasticDemo() {
	ElasticDemoLogAnalysis()
}
